package cn.chaos.nio.exercise;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
 */
public class MyHttp {


    private static final Logger LOGGER = LoggerFactory.getLogger(MyHttp.class);


    private static final int PORT = 11111;
    private static volatile boolean SHUTDOWN = false;
    private static final boolean BLOCK = false;



    private String getHeader(int code, String desc, long length, String mimeType) {
        String header = "HTTP/1.1 "+ code + " "+ desc + "\r\n" +
                "Server: JHttpServerNio2\r\n" +
                "Content-Length: "+ length + "\r\n" +
                "Content-Type: "+ mimeType + "\r\n\r\n";

        return header;
    }

    private final String docRoot;

    public MyHttp(String docRoot) {
        this.docRoot = docRoot;
    }

    public static void main(String[] args) {
        String docRoot = "/Users/chengchao/temp/mydocroot";

        try {
            new MyHttp(docRoot).start2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void start2() {

        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            Selector selector = Selector.open()
        ) {

            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(BLOCK)
                    .register(selector, SelectionKey.OP_ACCEPT);

            while (!SHUTDOWN) {

                SelectionKey key = null;


                if (selector.select() == 0) {
                    continue;
                }

                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> keys = selectionKeys.iterator();
                while (keys.hasNext()) {
                    key = keys.next();
                    keys.remove();

                    try {

                        if (!key.isValid()) {
                            key.cancel();
                            continue;
                        }

                        if (key.isAcceptable()) {
                            this.handleAcceptable(key);
                        } else if (key.isReadable()) {
                            this.handleReadable(key);
                        } else if (key.isWritable()) {
                            this.handleWritable(key);
                        }
                    } catch (CancelledKeyException e) {
                        key.cancel();
                        LOGGER.warn(e.getMessage());
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handleAcceptable(SelectionKey key) {

        ServerSocketChannel server = (ServerSocketChannel) key.channel();

        try {

            SocketChannel channel = server.accept();
            channel.configureBlocking(BLOCK);
            Selector selector = key.selector();
            channel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static final Pattern LF_PATTERN = Pattern.compile("\n");
    private static final Pattern SPACE_PATTERN = Pattern.compile("\\s+");
    private static final Pattern QM_PATTERN = Pattern.compile("\\?");


    private void handleReadable(SelectionKey key) {

        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(16 * 1024);

        try {

            if (channel.read(buffer) == -1) {
                key.cancel();
                channel.close();
                return;
            }

            buffer.flip();

            String requestText = StandardCharsets.US_ASCII.newDecoder().decode(buffer).toString();
            String firstLine = LF_PATTERN.split(requestText)[0];
            String[] firstLineArray = SPACE_PATTERN.split(firstLine);
            String fileNameAndParam = firstLineArray[1];
            String[] fileNameAndParamArray = QM_PATTERN.split(fileNameAndParam);

            String fileName = fileNameAndParamArray[0];
            if (fileName.endsWith("/")) {
                fileName += "index.html";
            }

            /*
             * channel.register(key.selector(), SelectionKey.OP_WRITE, fileName )
             */

            key.interestOps(SelectionKey.OP_WRITE);
            key.attach(fileName);

        } catch (IOException e) {
            LOGGER.error("handleReadable throw a Exception", e);
        }


    }

    private void handleWritable(SelectionKey key) {


        String fileName = (String) key.attachment();
        Path path = Paths.get(this.docRoot, fileName);

        try (SocketChannel channel = (SocketChannel) key.channel()) {
            String mimeType = "text/html; charset=utf-8";
            byte[] data;
            String desc;
            int code ;
            if (!path.toFile().canRead()) {
                code = 404;
                desc = "File Not Found";
                data = "<html><head><title>Not found</title></head><body><h1>404 File Not Found!</h1></body></html>".getBytes();

            } else {
                code = 200;
                desc = "OK";
                data = Files.readAllBytes(path);
                mimeType = URLConnection.getFileNameMap().getContentTypeFor(fileName);
            }
            channel.write(ByteBuffer.wrap(this.getHeader(code, desc, data.length, mimeType).getBytes(StandardCharsets.UTF_8)));
            channel.write(ByteBuffer.wrap(data));
            key.cancel();

        } catch (IOException e) {
            LOGGER.error("handleWritable throw a Exception", e);
        }
    }
}
